using System;
using System.Text;
using System.Threading.Tasks;
using HslCommunication.BasicFramework;
using HslCommunication.Core;
using HslCommunication.Core.Net;

namespace HslCommunication.Profinet.Fuji
{
	/// <summary>
	/// 富士PLC的SPB协议，详细的地址信息见api文档说明<br />
	/// Fuji PLC's SPB protocol. For detailed address information, see the api documentation.
	/// </summary>
	/// <remarks>
	/// 其所支持的地址形式如下：
	/// <list type="table">
	///   <listheader>
	///     <term>地址名称</term>
	///     <term>地址代号</term>
	///     <term>示例</term>
	///     <term>地址进制</term>
	///     <term>字操作</term>
	///     <term>位操作</term>
	///     <term>备注</term>
	///   </listheader>
	///   <item>
	///     <term>内部继电器</term>
	///     <term>M</term>
	///     <term>M100,M200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>√</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>输入继电器</term>
	///     <term>X</term>
	///     <term>X10,X20</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>√</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>输出继电器</term>
	///     <term>Y</term>
	///     <term>Y10,Y20</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>√</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>锁存继电器</term>
	///     <term>L</term>
	///     <term>L100,L200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>√</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>定时器的线圈</term>
	///     <term>TC</term>
	///     <term>TC100,TC200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>√</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>定时器的当前值</term>
	///     <term>TN</term>
	///     <term>TN100,TN200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>×</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>计数器的线圈</term>
	///     <term>CC</term>
	///     <term>CC100,CC200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>√</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>计数器的当前</term>
	///     <term>CN</term>
	///     <term>CN100,CN200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>×</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>数据寄存器</term>
	///     <term>D</term>
	///     <term>D1000,D2000</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>×</term>
	///     <term></term>
	///   </item>
	///   <item>
	///     <term>文件寄存器</term>
	///     <term>R</term>
	///     <term>R100,R200</term>
	///     <term>10</term>
	///     <term>√</term>
	///     <term>×</term>
	///     <term></term>
	///   </item>
	/// </list>
	/// </remarks>
	public class FujiSPBOverTcp : NetworkDeviceSoloBase
	{
		private byte station = 1;

		/// <summary>
		/// PLC的站号信息<br />
		/// PLC station number information
		/// </summary>
		public byte Station
		{
			get
			{
				return station;
			}
			set
			{
				station = value;
			}
		}

		/// <summary>
		/// 使用默认的构造方法实例化对象<br />
		/// Instantiate the object using the default constructor
		/// </summary>
		public FujiSPBOverTcp()
		{
			base.WordLength = 1;
			LogMsgFormatBinary = false;
			base.ByteTransform = new RegularByteTransform();
		}

		/// <summary>
		/// 使用指定的ip地址和端口来实例化一个对象<br />
		/// Instantiate an object with the specified IP address and port
		/// </summary>
		/// <param name="ipAddress">设备的Ip地址</param>
		/// <param name="port">设备的端口号</param>
		public FujiSPBOverTcp(string ipAddress, int port)
		{
			base.WordLength = 1;
			IpAddress = ipAddress;
			Port = port;
			LogMsgFormatBinary = false;
			base.ByteTransform = new RegularByteTransform();
		}

		/// <summary>
		/// 批量读取PLC的数据，以字为单位，支持读取X,Y,L,M,D,TN,CN,TC,CC,R具体的地址范围需要根据PLC型号来确认<br />
		/// Read PLC data in batches, in units of words. Supports reading X, Y, L, M, D, TN, CN, TC, CC, R. The specific address range needs to be confirmed according to the PLC model.
		/// </summary>
		/// <param name="address">地址信息</param>
		/// <param name="length">数据长度</param>
		/// <returns>读取结果信息</returns>
		public override OperateResult<byte[]> Read(string address, ushort length)
		{
			OperateResult<byte[]> operateResult = BuildReadCommand(station, address, length, isBool: false);
			if (!operateResult.IsSuccess)
			{
				return OperateResult.CreateFailedResult<byte[]>(operateResult);
			}
			OperateResult<byte[]> operateResult2 = ReadFromCoreServer(operateResult.Content);
			if (!operateResult2.IsSuccess)
			{
				return OperateResult.CreateFailedResult<byte[]>(operateResult2);
			}
			if (operateResult2.Content[0] != 58)
			{
				return new OperateResult<byte[]>(operateResult2.Content[0], "Read Faild:" + SoftBasic.ByteToHexString(operateResult2.Content, ' '));
			}
			if (Encoding.ASCII.GetString(operateResult2.Content, 9, 2) != "00")
			{
				return new OperateResult<byte[]>(operateResult2.Content[5], GetErrorDescriptionFromCode(Encoding.ASCII.GetString(operateResult2.Content, 9, 2)));
			}
			byte[] array = new byte[length * 2];
			for (int i = 0; i < array.Length / 2; i++)
			{
				ushort value = Convert.ToUInt16(Encoding.ASCII.GetString(operateResult2.Content, i * 4 + 6, 4), 16);
				BitConverter.GetBytes(value).CopyTo(array, i * 2);
			}
			return OperateResult.CreateSuccessResult(array);
		}

		/// <summary>
		/// 批量写入PLC的数据，以字为单位，也就是说最少2个字节信息，支持读取X,Y,L,M,D,TN,CN,TC,CC,R具体的地址范围需要根据PLC型号来确认<br />
		/// The data written to the PLC in batches, in units of words, that is, a minimum of 2 bytes of information. It supports reading X, Y, L, M, D, TN, CN, TC, CC, and R. The specific address range needs to be based on PLC model to confirm
		/// </summary>
		/// <param name="address">地址信息，举例，D100，R200，RC100，RT200</param>
		/// <param name="value">数据值</param>
		/// <returns>是否写入成功</returns>
		public override OperateResult Write(string address, byte[] value)
		{
			OperateResult<byte[]> operateResult = BuildWriteByteCommand(station, address, value);
			if (!operateResult.IsSuccess)
			{
				return operateResult;
			}
			OperateResult<byte[]> operateResult2 = ReadFromCoreServer(operateResult.Content);
			if (!operateResult2.IsSuccess)
			{
				return operateResult2;
			}
			if (operateResult2.Content[0] != 58)
			{
				return new OperateResult<byte[]>(operateResult2.Content[0], "Read Faild:" + SoftBasic.ByteToHexString(operateResult2.Content, ' '));
			}
			if (Encoding.ASCII.GetString(operateResult2.Content, 9, 2) != "00")
			{
				return new OperateResult<byte[]>(operateResult2.Content[5], GetErrorDescriptionFromCode(Encoding.ASCII.GetString(operateResult2.Content, 9, 2)));
			}
			return OperateResult.CreateSuccessResult();
		}

		/// <inheritdoc cref="M:HslCommunication.Profinet.Fuji.FujiSPBOverTcp.Read(System.String,System.UInt16)" />
		public override async Task<OperateResult<byte[]>> ReadAsync(string address, ushort length)
		{
			OperateResult<byte[]> command = BuildReadCommand(station, address, length, isBool: false);
			if (!command.IsSuccess)
			{
				return OperateResult.CreateFailedResult<byte[]>(command);
			}
			OperateResult<byte[]> read = await ReadFromCoreServerAsync(command.Content);
			if (!read.IsSuccess)
			{
				return OperateResult.CreateFailedResult<byte[]>(read);
			}
			if (read.Content[0] != 58)
			{
				return new OperateResult<byte[]>(read.Content[0], "Read Faild:" + SoftBasic.ByteToHexString(read.Content, ' '));
			}
			if (Encoding.ASCII.GetString(read.Content, 9, 2) != "00")
			{
				return new OperateResult<byte[]>(read.Content[5], GetErrorDescriptionFromCode(Encoding.ASCII.GetString(read.Content, 9, 2)));
			}
			byte[] Content = new byte[length * 2];
			for (int i = 0; i < Content.Length / 2; i++)
			{
				ushort tmp = Convert.ToUInt16(Encoding.ASCII.GetString(read.Content, i * 4 + 6, 4), 16);
				BitConverter.GetBytes(tmp).CopyTo(Content, i * 2);
			}
			return OperateResult.CreateSuccessResult(Content);
		}

		/// <inheritdoc cref="M:HslCommunication.Profinet.Fuji.FujiSPBOverTcp.Write(System.String,System.Byte[])" />
		public override async Task<OperateResult> WriteAsync(string address, byte[] value)
		{
			OperateResult<byte[]> command = BuildWriteByteCommand(station, address, value);
			if (!command.IsSuccess)
			{
				return command;
			}
			OperateResult<byte[]> read = await ReadFromCoreServerAsync(command.Content);
			if (!read.IsSuccess)
			{
				return read;
			}
			if (read.Content[0] != 58)
			{
				return new OperateResult<byte[]>(read.Content[0], "Read Faild:" + SoftBasic.ByteToHexString(read.Content, ' '));
			}
			if (Encoding.ASCII.GetString(read.Content, 9, 2) != "00")
			{
				return new OperateResult<byte[]>(read.Content[5], GetErrorDescriptionFromCode(Encoding.ASCII.GetString(read.Content, 9, 2)));
			}
			return OperateResult.CreateSuccessResult();
		}

		/// <inheritdoc />
		public override string ToString()
		{
			return $"FujiSPBOverTcp[{IpAddress}:{Port}]";
		}

		private static string AnalysisIntegerAddress(int address)
		{
			string text = address.ToString("D4");
			return text.Substring(2) + text.Substring(0, 2);
		}

		/// <summary>
		/// 解析数据地址成不同的三菱地址类型
		/// </summary>
		/// <param name="address">数据地址</param>
		/// <returns>地址结果对象</returns>
		public static OperateResult<string> FujikAnalysisAddress(string address)
		{
			OperateResult<string> operateResult = new OperateResult<string>();
			try
			{
				switch (address[0])
				{
				case 'X':
				case 'x':
					operateResult.Content = "01" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
					break;
				case 'Y':
				case 'y':
					operateResult.Content = "00" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
					break;
				case 'M':
				case 'm':
					operateResult.Content = "02" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
					break;
				case 'L':
				case 'l':
					operateResult.Content = "03" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
					break;
				case 'T':
				case 't':
					if (address[1] == 'N' || address[1] == 'n')
					{
						operateResult.Content = "0A" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
						break;
					}
					if (address[1] == 'C' || address[1] == 'c')
					{
						operateResult.Content = "04" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
						break;
					}
					throw new Exception(StringResources.Language.NotSupportedDataType);
				case 'C':
				case 'c':
					if (address[1] == 'N' || address[1] == 'n')
					{
						operateResult.Content = "0B" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
						break;
					}
					if (address[1] == 'C' || address[1] == 'c')
					{
						operateResult.Content = "05" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
						break;
					}
					throw new Exception(StringResources.Language.NotSupportedDataType);
				case 'D':
				case 'd':
					operateResult.Content = "0C" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
					break;
				case 'R':
				case 'r':
					operateResult.Content = "0D" + AnalysisIntegerAddress(Convert.ToUInt16(address.Substring(1), 10));
					break;
				default:
					throw new Exception(StringResources.Language.NotSupportedDataType);
				}
			}
			catch (Exception ex)
			{
				operateResult.Message = ex.Message;
				return operateResult;
			}
			operateResult.IsSuccess = true;
			return operateResult;
		}

		/// <summary>
		/// 计算指令的和校验码
		/// </summary>
		/// <param name="data">指令</param>
		/// <returns>校验之后的信息</returns>
		public static string CalculateAcc(string data)
		{
			byte[] bytes = Encoding.ASCII.GetBytes(data);
			int num = 0;
			for (int i = 0; i < bytes.Length; i++)
			{
				num += bytes[i];
			}
			return num.ToString("X4").Substring(2);
		}

		/// <summary>
		/// 创建一条读取的指令信息，需要指定一些参数
		/// </summary>
		/// <param name="station">PLCd的站号</param>
		/// <param name="address">地址信息</param>
		/// <param name="length">数据长度</param>
		/// <param name="isBool">是否位读取</param>
		/// <returns>是否成功的结果对象</returns>
		public static OperateResult<byte[]> BuildReadCommand(byte station, string address, ushort length, bool isBool)
		{
			OperateResult<string> operateResult = FujikAnalysisAddress(address);
			if (!operateResult.IsSuccess)
			{
				return OperateResult.CreateFailedResult<byte[]>(operateResult);
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(':');
			stringBuilder.Append(station.ToString("X2"));
			stringBuilder.Append("09");
			stringBuilder.Append("FFFF");
			stringBuilder.Append("00");
			stringBuilder.Append("00");
			stringBuilder.Append(operateResult.Content);
			stringBuilder.Append(length.ToString("D4"));
			stringBuilder.Append("\r\n");
			return OperateResult.CreateSuccessResult(Encoding.ASCII.GetBytes(stringBuilder.ToString()));
		}

		/// <summary>
		/// 创建一条别入byte数据的指令信息，需要指定一些参数，按照字单位
		/// </summary>
		/// <param name="station">站号</param>
		/// <param name="address">地址</param>
		/// <param name="value">数组值</param>
		/// <returns>是否创建成功</returns>
		public static OperateResult<byte[]> BuildWriteByteCommand(byte station, string address, byte[] value)
		{
			OperateResult<string> operateResult = FujikAnalysisAddress(address);
			if (!operateResult.IsSuccess)
			{
				return OperateResult.CreateFailedResult<byte[]>(operateResult);
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(':');
			stringBuilder.Append(station.ToString("X2"));
			stringBuilder.Append((9 + value.Length / 2).ToString("X2"));
			stringBuilder.Append("FFFF");
			stringBuilder.Append("01");
			stringBuilder.Append("00");
			stringBuilder.Append(operateResult.Content);
			stringBuilder.Append((value.Length / 2).ToString("D4"));
			byte[] array = new byte[value.Length * 2];
			for (int i = 0; i < value.Length / 2; i++)
			{
				SoftBasic.BuildAsciiBytesFrom(BitConverter.ToUInt16(value, i * 2)).CopyTo(array, 4 * i);
			}
			stringBuilder.Append(Encoding.ASCII.GetString(array));
			stringBuilder.Append("\r\n");
			return OperateResult.CreateSuccessResult(Encoding.ASCII.GetBytes(stringBuilder.ToString()));
		}

		/// <summary>
		/// 根据错误码获取到真实的文本信息
		/// </summary>
		/// <param name="code">错误码</param>
		/// <returns>错误的文本描述</returns>
		public static string GetErrorDescriptionFromCode(string code)
		{
			return code switch
			{
				"01" => StringResources.Language.FujiSpbStatus01, 
				"02" => StringResources.Language.FujiSpbStatus02, 
				"03" => StringResources.Language.FujiSpbStatus03, 
				"04" => StringResources.Language.FujiSpbStatus04, 
				"05" => StringResources.Language.FujiSpbStatus05, 
				"06" => StringResources.Language.FujiSpbStatus06, 
				"07" => StringResources.Language.FujiSpbStatus07, 
				"09" => StringResources.Language.FujiSpbStatus09, 
				"0C" => StringResources.Language.FujiSpbStatus0C, 
				_ => StringResources.Language.UnknownError, 
			};
		}
	}
}
